package com.snda.sc.hps;

import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.annotation.PreDestroy;
import javax.annotation.Resource;

import org.apache.http.conn.ConnectTimeoutException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.snda.sc.auth.AuthResult;
import com.snda.sc.exception.ConfigureException;
import com.snda.sc.hps.HPSConnector.HPSResponse;
import com.snda.sc.store.IRedisStore;
import com.snda.sc.utils.Charsets;
import com.snda.sc.utils.CommonUtils;
import com.snda.sc.utils.Constants.ResultCode;
import com.snda.sc.utils.Keys;
import com.snda.sc.utils.Logs;

@Component("hpsBinder")
public class HpsBinder implements IHpsBinder {

	public static final String HPS_CONF_FILE = "hps.json";
	public static final String SP_CONF_FILE = "sp.json";

	@Resource(name = "redisStore")
	private IRedisStore redisStore;

	private volatile Map<String, String> hpsMap = new HashMap<>();
	private volatile Map<String, SP> spMap = new HashMap<>();

	private HPSConnector connector = new HPSConnector();

	@Override
	public void configure(File confFile) throws Exception {
		String fileName = confFile.getName();
		if (HPS_CONF_FILE.equals(fileName)) {
			configHps(confFile);
		} else if (SP_CONF_FILE.equals(fileName)) {
			configSp(confFile);
		}
	}

	private void configHps(File hpsConfFile) throws IOException {
		JSONObject conf = JSON.parseObject(new String(Files.readAllBytes(hpsConfFile.toPath()), Charsets.UTF_8));
		Map<String, String> newHpsMap = new HashMap<>();
		Map<String, String> oldHpsMap = hpsMap;
		for (Entry<String, Object> entry : conf.entrySet()) {
			newHpsMap.put(entry.getKey(), String.valueOf(entry.getValue()));
		}

		hpsMap = newHpsMap;
		oldHpsMap.clear();
	}

	private void configSp(File spConfFile) throws IOException {
		JSONObject conf = JSON.parseObject(new String(Files.readAllBytes(spConfFile.toPath()), Charsets.UTF_8));
		Map<String, SP> newSpMap = new HashMap<>();
		Map<String, SP> oldSpMap = spMap;

		for (Entry<String, Object> entry : conf.entrySet()) {
			String appId = entry.getKey();
			JSONObject item = (JSONObject) entry.getValue();
			SP sp = new SP(appId, item.getString("osapUser"), item.getString("secret"), item.getString("spid"));

			if (!StringUtils.hasLength(sp.getOsapUser())) throw new ConfigureException("secret property is required by key: "
					+ appId);
			if (!StringUtils.hasLength(sp.getSecret())) throw new ConfigureException("osapUser property is required by key: "
					+ appId);
			if (!StringUtils.hasLength(sp.getSpid())) throw new ConfigureException("spid property is required by key: "
					+ appId);

			String url = item.getString("url");
			if (StringUtils.hasLength(url)) sp.setUrl(url);
			newSpMap.put(appId, sp);
		}

		spMap = newSpMap;
		oldSpMap.clear();
	}

	@Override
	public Set<String> getConfigFileNames() {
		return CommonUtils.initSet(HPS_CONF_FILE, SP_CONF_FILE);
	}

	@Override
	public boolean watchModify() {
		return true;
	}

	@Override
	public Scope getScope() {
		return Scope.GLOBAL;
	}

	@Override
	public void bind(HPSOption option, AuthResult result) {

		// 如果Redis中有HPS绑定结果，则直接返回，否则去HPS绑定
		if (tryQueryCache(option, result)) return;

		String companyId = hpsMap.get(option.plt);
		if (!StringUtils.hasLength(companyId)) {
			result.setResult(ResultCode.AUTH_ERR_HPS_NO_COMPAYNID);
			result.setCause("No companyId found by plt: " + option.plt + " in hps.json.");
			return;
		}

		SP sp = spMap.get(option.appId);
		if (sp == null) {
			result.setResult(ResultCode.AUTH_ERR_HPS_NO_SP);
			result.setCause("No sp info found by appId: " + option.appId + " in sp.json.");
			return;
		}

		int statusCode = -1;
		JSONObject body = null;
		HPSResponse response = null;
		try {
			response = connector.get(sp, companyId, option);
			statusCode = response.getStatusCode();
			body = JSONObject.parseObject(response.getContent());
		} catch (JSONException e) {
			result.setResult(ResultCode.AUTH_ERR_HPS_CONTENT_PARSE);
			result.setCause("Unable parse '" + response.getContent() + "' to json.");
			return;
		} catch (ConnectTimeoutException e) {
			result.setResult(ResultCode.AUTH_ERR_HPS_CONNECT_TIMEOUT);
			result.setCause("Connect to HPS server timeout.");
			return;
		} catch (SocketTimeoutException e) {
			result.setResult(ResultCode.AUTH_ERR_HPS_READ_TIMEOUT);
			result.setCause("Read content from HPS server timeout.");
			return;
		} catch (Exception e) {
			result.setResult(ResultCode.AUTH_ERR_HPS_NET);
			result.setCause("Unable connect to HPS server.");
			return;
		}

		if (statusCode != HttpURLConnection.HTTP_OK) {
			result.setResult(ResultCode.AUTH_ERR_HPS_HTTP);
			result.setCause("HPS server HTTP statusCode: " + statusCode + " error.");
			return;
		}

		int hpsCode = body.getIntValue("return_code");
		if (hpsCode != 0) {
			result.setResult(ResultCode.AUTH_ERR_HPS_CODE);
			result.setCause("HPS response return_code: " + hpsCode + " error.");
			return;
		}

		JSONObject hpsData = body.getJSONObject("data");
		if (hpsData == null) {
			result.setResult(ResultCode.AUTH_ERR_HPS_DATA);
			result.setCause("HPS response data field is null.");
			return;
		}

		String pt = hpsData.getString("Pt");
		String sndaId = hpsData.getString("SndaId");
		if (pt == null || sndaId == null) {
			result.setResult(ResultCode.AUTH_ERR_HPS_DATA);
			result.setCause("HPS response data field is null.");
			return;
		}

		result.setPt(pt);
		result.setUid(sndaId);

		cacheHpsResult(option, pt, sndaId);
	}

	/**
	 * 尝试从Redis中加载HPS缓存数据，如果没有的话返回false
	 * 
	 * @return
	 */
	private boolean tryQueryCache(HPSOption option, AuthResult result) {
		JSONObject cacheItem = null;
		try {
			String hpsCache = redisStore.get(Keys.hpsCacheKey(option.plt, option.snsUid));
			if (!StringUtils.hasLength(hpsCache)) return false;
			cacheItem = JSONObject.parseObject(hpsCache);
		} catch (Exception e) {
			return false;
		}
		String pt = cacheItem.getString("pt");
		String uid = cacheItem.getString("sndaId");
		if (pt == null || uid == null) return false;
		result.setPt(pt);
		result.setUid(uid);
		return true;
	}

	private void cacheHpsResult(HPSOption option, String pt, String sndaId) {
		Map<String, String> hpsCache = CommonUtils.initMap("pt", pt, "sndaId", sndaId);
		try {
			redisStore.setHpsExpire(Keys.hpsCacheKey(option.plt, option.snsUid), JSONObject.toJSONString(hpsCache));
		} catch (Exception e) {
			Logs.logException("Save HPS result in redis error.", e);
		}
	}

	@PreDestroy
	public void destroy() {
		connector.destroy();
	}
}
